package test3.parser;

import java.util.ArrayList;

/**
 * Created by Claudiu-Florin FILIMON
 * Date: Aug 2, 2007
 * Time: 11:00:35 AM
 * Revision: 0
 */
public class TokenManager
{
    private char[] dataBuf;
    private int dataPos;

    private ArrayList tokens;
    private int tokenPos;


    public TokenManager(char[] data)
    {
        setData(data);
    }

    public void setData(char[] data)
    {
        this.dataBuf = data;
        reParseFile();
    }

    public String getText(int startPos, int endPos)
    {
        return new String(dataBuf, startPos, endPos - startPos);
    }

    public void initSearch()
    {
        tokenPos = 0;
    }

    public Token getNextToken()
    {
        return getNextToken(false);
    }

    public Token getNextToken(boolean showComments)
    {
        while (tokenPos < tokens.size())
        {
            Token t = (Token) tokens.get(tokenPos++);

            if ((t.getType() != Token.TYPE_STR_COMMENT) || ((t.getType() == Token.TYPE_STR_COMMENT) && showComments))
                return t;
        }

        return null;
    }

    private void skipCppComment()
    {
        int p = dataPos+2;

        while ((p < dataBuf.length) && (dataBuf[p] != '\n'))
            p++;

        Token t = new Token(this);
        t.setType(Token.TYPE_STR_COMMENT);
        t.setBounds(dataPos, p);
        tokens.add(t);

        dataPos = p;
    }

    private void skipCComment()
    {
        int p = dataPos + 2;

        while ((p < dataBuf.length + 1) && (!((dataBuf[p] == '*') && (dataBuf[p + 1] == '/'))))
            p++;

        Token t = new Token(this);
        t.setType(Token.TYPE_STR_COMMENT);

        if ((p < dataBuf.length + 1) && ((dataBuf[p] == '*') && (dataBuf[p + 1] == '/')))
            p += 2;
        else
            t.setError(ParseError.ERROR_COMMENT_UNTERMINATED);
        t.setBounds(dataPos, p);
        tokens.add(t);

        dataPos = p;
    }

    private boolean isWhiteChar(char c)
    {
        return ((c == ' ') || (c == '\n') || (c == '\t'));
    }

    private void skipWhitespace()
    {
        while ((dataPos < dataBuf.length) && isWhiteChar(dataBuf[dataPos]))
            dataPos++;
    }

    private void skipUseless()
    {
        while (dataPos < dataBuf.length)
        {
            char c = dataBuf[dataPos];

            if (isWhiteChar(c))
                skipWhitespace();
            else
                if (c == '/')
                {
                    if (dataPos < dataBuf.length + 1)
                    {
                        if (dataBuf[dataPos + 1] == '/')
                            skipCppComment();
                        else
                            if (dataBuf[dataPos + 1] == '*')
                                skipCComment();
                            else
                                return;
                    }
                    else
                        return;
                }
                else
                    return;

        }
    }

    private boolean expect(char c, int type)
    {
        if (dataBuf[dataPos] == c)
        {
            Token t = new Token(this);
            t.setType(type);
            t.setBounds(dataPos, dataPos + 1);

            dataPos++;
            return true;
        }
        return false;
    }

    private boolean expect(char c1, char c2, int type)
    {
        if (dataPos >= dataBuf.length + 1)
            return false;
        if ((dataBuf[dataPos] == c1) && (dataBuf[dataPos + 1] == c2))
        {
            Token t = new Token(this);
            t.setType(type);
            t.setBounds(dataPos, dataPos + 2);

            dataPos += 2;
            return true;
        }
        return false;
    }

    private void expectToken(int type)
    {
        int p = dataPos;

        if (type == Token.TYPE_STR_IDENTIFIER)
        {
            while ((p < dataBuf.length) && isAlphaNumChar(dataBuf[p]))
                p++;
        }
        else
        {
            if ((p < dataBuf.length + 1) && (dataBuf[p] == '0') && ((dataBuf[p + 1] == 'x') || (dataBuf[p + 1] == 'X')))
            {
                p += 2;
                while ((p < dataBuf.length + 1) && isNumericHexChar(dataBuf[p]))
                    p++;
            }
            else
            {
                while ((p < dataBuf.length + 1) && isNumericChar(dataBuf[p]))
                    p++;
            }

            if ((p < dataBuf.length + 1) && ((dataBuf[p] == 'L') || (dataBuf[p] == 'l')))
                p++;
        }

        Token t = new Token(this);
        t.setType(type);
        t.setBounds(dataPos, p);
        tokens.add(t);

        dataPos = p;
    }

    private void expectString()
    {
        int p = dataPos + 1;
        boolean continueSearch = true;

        while ((p < dataBuf.length) && continueSearch)
        {
            if ((dataBuf[p] == '\n') || ((dataBuf[p] == '"') && (dataBuf[p - 1] != '\\')))
                continueSearch = false;
            else
                p++;
        }

        Token t = new Token(this);
        t.setType(Token.TYPE_STR_STRING);
        t.setBounds(dataPos, p);

        if (dataBuf[p] != '"')
            t.setError(ParseError.ERROR_STRING_UNTERMINATED);
        tokens.add(t);

        dataPos = p;
    }

    private void expectChar()
    {
        int p = dataPos + 1;

        boolean continueSearch = true;

        while ((p < dataBuf.length) && continueSearch)
        {
            if (((dataBuf[p] == '\'') && (dataBuf[p - 1] != '\\')) || (dataBuf[p] == '\n'))
                continueSearch = false;
            else
                p++;
        }

        Token t = new Token(this);
        t.setType(Token.TYPE_STR_CHAR);
        t.setBounds(dataPos, p);

        if (dataBuf[p] != '\'')
            t.setError(ParseError.ERROR_CHARLITERAL_TOOLONG);

        tokens.add(t);
        dataPos = p;

    }

    private boolean isNumericChar(char c)
    {
        return ((c >= '0') && (c <= '9'));
    }

    private boolean isNumericHexChar(char c)
    {
        return isNumericChar(c) || ((c >= 'a') && (c <= 'f')) || ((c >= 'A') && (c <= 'F'));
    }

    private boolean isAlphaChar(char c)
    {
        return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || (c == '_'));
    }

    private boolean isAlphaNumChar(char c)
    {
        return isAlphaChar(c) || isNumericChar(c);
    }

    private void parseToken()
    {
        skipUseless();

        if (dataPos >= dataBuf.length)
            return;

        char c = dataBuf[dataPos];

        if (isNumericChar(c))
        {
            expectToken(Token.TYPE_STR_NUMBER);
            return;
        }

        if (isAlphaChar(c))
        {
            expectToken(Token.TYPE_STR_IDENTIFIER);
            return;
        }

        if (c == '"')
        {
            expectString();
            return;
        }

        if (c == '\'')
        {
            expectChar();
            return;
        }

        if (expect('+', '=', Token.TYPE_OP_ADDE))
            return;
        if (expect('-', '=', Token.TYPE_OP_SUBE))
            return;
        if (expect('*', '=', Token.TYPE_OP_MULE))
            return;
        if (expect('/', '=', Token.TYPE_OP_DIVE))
            return;
        if (expect('%', '=', Token.TYPE_OP_MODE))
            return;


        if (expect('+', Token.TYPE_OP_ADD))
            return;
        if (expect('-', Token.TYPE_OP_SUB))
            return;
        if (expect('*', Token.TYPE_OP_MUL))
            return;
        if (expect('/', Token.TYPE_OP_DIV))
            return;
        if (expect('%', Token.TYPE_OP_MOD))
            return;

        Token t = new Token(this);
        t.setType(Token.TYPE_UNKNOWN);
        t.setBounds(dataPos, dataPos + 1);
        dataPos++;
        tokens.add(t);
    }

    public void reParseFile()
    {
        tokens = new ArrayList();
        dataPos = 0;

        while (dataPos < dataBuf.length)
        {
            parseToken();
        }

        Token t = new Token(this);
        t.setType(Token.TYPE_END_OF_FILE);
        t.setBounds(dataPos, dataPos);
        tokens.add(t);
    }
}
